/*
    Contact info:
    bhepple@freeshell.org
    http://bhepple.freeshell.org/scrmgr

    Copyright (C) 1991-2008 Bob Hepple

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; see the file COPYING.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.

*/
/* All references to 'curses' are here. In principle, it should be
   possible to use another windowing system by hacking here.

   Athena toolkit supported through calls to smxaw.c

   Olit toolkit supported through calls to smxview.c
   (it needs work). Use xctool instead. */

#include <unistd.h>
#if USE_NCURSES
#include <ncurses/curses.h>
#else
#include <curses.h>
#endif

#include <sys/stat.h>		/* for stat */
#define SM_CLASS extern
#include "smlib.h"
#include "smio.h"

#ifdef SM_XVIEW
#include "smxview.h"
#endif

#ifdef SM_XAW
#include "smxaw.h"
#endif

CHAR *smio_rcsid(void) {
    return("$Id: smio.c,v 1.7 2008/02/19 13:15:50 bhepple Exp $");
}

#ifdef IBM_LINE_DRAWING /* Line drawing characters ... */

#define VERT		179
#define HORIZ		196
#define ULEFT		218
#define URIGHT		191
#define LLEFT		192
#define LRIGHT		217

#else

#define VERT		'|'
#define HORIZ		'-'
#define ULEFT		'+'
#define URIGHT		'+'
#define LLEFT		'+'
#define LRIGHT		'+'

#endif

#ifndef A_NORMAL
#define A_NORMAL 0
#endif

SM_SOFTKEY 		smio_current_skey = NULL;
static CHAR		*smio_current_argument = NULL;
static int		smio_current_start;
static SM_MENU		smio_root_menu = NULL;
static CHAR		smio_menu_help_message[] = "\\Help";

#ifdef SM_XVIEW
int	smio_using_xview	= 0;
#endif
#ifdef SM_XAW
int	smio_using_xaw		= 0;
#endif
int	smio_using_curses 	= 0;

/* The callback functions are implemented as a stack. Any routine can
   push a function onto the stack. If the function is called and returns
   0 then it is left on the stack. If it returns non-zero, it is removed
   the stack and the previous function is called. This is, then, a 
   heirarchy of calls.
   
   The function is called as:
   
   f(int command, void *data)
   
   Mouse picks, if any, can be examined later with smio_enq_mouse.

*/
struct smio_callback_s {
    f_ptr			lambda;
    void			*object;
    struct smio_callback_s 	*next;
};
typedef struct smio_callback_s *SMIO_CALLBACK;
static SMIO_CALLBACK	smio_callback = NULL;

static WINDOW		*fkey_window, *msg_window, *menu_window;

struct window_control {
    WINDOW	*window;
    int		num_rows,
    		num_cols,
    		origin_row,
		origin_col;
};
static struct window_control wstack[SM_MAX_POPUP];

static int		top_of_wstack 	= 0;
static int		cursor_valid 	= TRUE;
int			smio_insert_state 	= TRUE;
static int		meta_flag 	= 0;
static int 		pushed_char	= 0;
static int		pushed_command	= 0;

int			smio_button,
    			smio_mouse_row,
    			smio_mouse_col,
    			smio_time_delta;

static ARRAY		main_keymap = NULL;

/* Create mapping table for key->command */
static void smio_map_key ARG2(int, 	key, 
			      CHAR *,	string)
{
    /* Only use the first definition for a key */
    if (!main_keymap || !sm_lookup_string(main_keymap, key))
	main_keymap = sm_set_keyword(main_keymap, key, string, NULL);
}

void smio_abort ARG1(CHAR *, message)
{
#ifdef SM_XVIEW
    if (smio_using_xview) {
	
    } else
#endif
#ifdef SM_XAW
    if (smio_using_xaw) {
	
    } else
#endif
	if (smio_using_curses)
	    endwin();
    write(1, "\n", 1);
    write(1, message, strlen(message));
    write(1, "\n", 1);
    exit(1);
}

static void smio_box ARG1(int, window)
{
    box(wstack[window].window, 0, 0);
}

/* Put terminal back to ground state - as we found it */
void	smio_susp()
{
    if (smio_using_curses) {
	
#if 0
	touchwin(stdscr);
	move(0, 0);
	clrtobot();
	refresh(); /* to clear screen */
#endif
	resetterm();
    }
}

static void refresh_windows()
{
    wnoutrefresh(fkey_window);
    wnoutrefresh(msg_window);
    if (menu_window)
	wnoutrefresh(menu_window);
    wnoutrefresh(wstack[top_of_wstack].window);
    doupdate();
}

/* Put terminal back to the state we want */
void	smio_res()
{
    if (smio_using_curses) {
	fixterm();
	clearok(curscr, TRUE);
	/* needed at Linux 2.0.36 otherwise keypad would be off on return from
	   external function: */
	keypad(wstack[top_of_wstack].window, TRUE); 
	refresh_windows();
    }
}

void smio_init ARG3(int, doit, int, argc, CHAR **, argv)
{
    /*
     * Do the initscr only once per process since endwin does not release
     * heap that initscr has grabbed.
     */
    if (doit) {
	CHAR	*p;
	
	if ((p = getenv("SCRMGR_USE_MENUS")) != NULL)
	    sm_use_menus = 1;
	
	p = getenv("DISPLAY");
#ifdef SM_XVIEW
	g = getenv("IN_CTOOL");
	if (p && *p && !g) {
	    smio_using_xview = 1;
	    sm_use_menus = 0;
	    smxview_init(doit, argc, argv);
	} else {
#elif SM_XAW
	g = getenv("IN_CTOOL");
	if (p && *p && !g) {
	    smio_using_xaw = 1;
	    smxaw_init(doit, argc, argv);
	} else {
#elif HAS_XCTOOL	
	g = getenv("IN_CTOOL");
	if (p && *p && !g) { /* Run under xctool */
	    CHAR 	**new_argv, **arg;
	    int		i;

	    arg = new_argv = (CHAR **)xmalloc((INDEX) sizeof(CHAR *) * (argc + 3));
	    g = getenv("XCTOOL");
	    if (g && *g) 
		/* If they want this to fail they will set to "" */
		*arg++ = g;
	    else
		*arg++ = "xctool";
	    if (!g || *g) { /* ie XCTOOL != "" */
		*arg++ = "-rc";
		for (i = 0; i < argc; i++)
		    *arg++ = argv[i];
		*arg++ = NULL;
		i = vfork();
		if ((i < 0) || /* vfork failed */
		    ((i == 0) && (execvp(new_argv[0], new_argv) < 0))) { 
		    /* execlp failed */
		    CHAR buf[100];
		    
		    sprintf(buf, "%s: Can't run %s", argv[0], new_argv[0]);
		    perror(buf);
		    _exit(1);
		} else { /* In the parent */
		    sleep(1); 
		    /* Probably enough time for child to die if its going to */
		    if (kill(i, 19) == 0) 
			/* should be harmless, just see if it's still alive */
			_exit(0); /* Let the child take over in background */
		    /* Otherwise, carry on as if nothing happened! */
		}
	    } /* XCTOOL=="" ... just proceed as normal */
	    free(new_argv);
	}
#endif /* SM_XVIEW */


	    initscr();
	    smio_using_curses = 1;
	    touchwin(stdscr);
	    move(0, 0);
	    clrtobot();
	    refresh(); /* to clear screen */
	    
	    /* Put this here in case we are coming through this routine after
	     * a Sunview/X11 resize window interupt.
	     */
	    if ((LINES < 24) || (COLS < 80))
		smio_abort("Need at least 24 rows and 80 columns.");
	    
	    /* Use raw instead of cbreak() ... it allows ^C etc to pass
	       through instead of giving an interrupt */
	    raw();
	    nonl();
	    noecho();
	    
	    if (sm_use_menus)
		menu_window = newwin(1, COLS, 0, 0);
	    else
		menu_window = NULL;

	    /* Trick smio_popup to create the first window ... */
	    top_of_wstack = -1;
	    if (smio_popup(LINES - 3 - sm_use_menus, COLS, sm_use_menus, 0) < 0)
		smio_abort("Can't create main window!");
	    
	    if ((fkey_window = newwin(2, COLS, LINES-2, 0)) == NULL)
		smio_abort("Can't create softkey label window.");
	    
	    if ((msg_window = newwin(1, COLS, LINES-3, 0)) == NULL)
		smio_abort("Can't create message window.");
	    
	    def_prog_mode();

#if HAS_XCTOOL || SM_XVIEW || SM_XAW
	}
#endif
    
    } else
	smio_using_curses = 0;
	
    /* Set up the default key mapping tables */
    smio_map_key(SM_CTRL('A'),		"sm_home_field_c");
    smio_map_key(SM_CTRL('B'),		"sm_left_c");
    smio_map_key(SM_CTRL('C'),		"sm_quit_c");
    smio_map_key(SM_CTRL('D'),		"sm_del_char_c");
    smio_map_key(SM_CTRL('E'),		"sm_end_field_c");
    smio_map_key(SM_CTRL('F'),		"sm_right_c");
    smio_map_key(SM_CTRL('H'),		"sm_del_back_c");
    smio_map_key(SM_CTRL('I'),		"sm_next_field_c");
    smio_map_key(SM_CTRL('J'),		"sm_next_line_c");
    smio_map_key(SM_CTRL('K'),		"sm_clear_field_c");
    smio_map_key(SM_CTRL('L'),		"sm_refresh_c");
    smio_map_key(SM_CTRL('M'),		"sm_next_line_c");
    smio_map_key(SM_CTRL('N'),		"sm_down_c");
    smio_map_key(SM_CTRL('P'),		"sm_up_c");
    smio_map_key(SM_CTRL('T'),		"sm_transpose_chars_c");
    smio_map_key(SM_CTRL('U'),		"sm_toggle_case_c");
    smio_map_key(SM_CTRL('V'),		"sm_tell_version_c");
    smio_map_key(SM_CTRL('W'),		"sm_delete_field_c");
    smio_map_key(SM_CTRL('Y'),		"sm_yank_field_c");
    /* If \177 (del) is mapped by curses, then it gets converted to
       SM_META(something) ... */
    smio_map_key('\177', 		"sm_del_back_c");
    
    smio_map_key(SM_META('<'), 		"sm_first_screen_c");
    smio_map_key(SM_META('>'), 		"sm_last_screen_c");
    smio_map_key(SM_META('A'), 		"sm_first_screen_c");
    smio_map_key(SM_META('a'), 		"sm_home_screen_c");
    smio_map_key(SM_META(SM_CTRL('A')), "sm_store_file_as_c");
    smio_map_key(SM_META('B'), 		"sm_left_word_c");
    smio_map_key(SM_META('b'), 		"sm_left_word_c");
    smio_map_key(SM_META('c'),		"sm_copy_data_c");
    smio_map_key(SM_META('C'),		"sm_copy_data_c");
    smio_map_key(SM_META(SM_CTRL('C')), "sm_clear_file_c");
    smio_map_key(SM_META('d'),		"sm_del_word_c");
    smio_map_key(SM_META('D'),		"sm_delete_data_c");
    smio_map_key(SM_META('E'),		"sm_last_screen_c");
    smio_map_key(SM_META('e'),		"sm_end_screen_c");
    smio_map_key(SM_META('F'),		"sm_right_word_c");
    smio_map_key(SM_META('f'),		"sm_right_word_c");
    smio_map_key(SM_META('G'),		"sm_goto_record_c");
    smio_map_key(SM_META('g'),		"sm_goto_record_c");
    smio_map_key(SM_META('H'),		"sm_help_c");
    smio_map_key(SM_META('h'),		"sm_help_c");
    smio_map_key(SM_META('I'),		"sm_insert_data_c");
    smio_map_key(SM_META('i'),		"sm_toggle_ins_c");
    smio_map_key(SM_META(SM_CTRL('I')), "sm_prev_field_c");
    smio_map_key(SM_META('K'),		"sm_clear_screen_c");
    smio_map_key(SM_META('k'),		"sm_clear_screen_c");
    smio_map_key(SM_META('L'),		"sm_edit_master_c");
    smio_map_key(SM_META('l'),		"sm_search_backwards_c");
    smio_map_key(SM_META(SM_CTRL('L')), "sm_load_file_c");
    smio_map_key(SM_META('M'), 		"sm_popup_c");
    smio_map_key(SM_META('m'), 		"sm_popup_c");
    smio_map_key(SM_META(SM_CTRL('N')), "sm_no_c");
    smio_map_key(SM_META('N'),		"sm_next_screen_c");
    smio_map_key(SM_META('n'),		"sm_next_screen_c");
    smio_map_key(SM_META('P'),		"sm_prev_screen_c");
    smio_map_key(SM_META('p'),		"sm_prev_screen_c");
    smio_map_key(SM_META('R'),		"sm_replace_all_c");
    smio_map_key(SM_META('r'),		"sm_search_rep_c");
    smio_map_key(SM_META('S'),		"sm_again_c");
    smio_map_key(SM_META('s'),		"sm_search_c");
    smio_map_key(SM_META(SM_CTRL('S')), "sm_store_file_c");
    smio_map_key(SM_META('T'), 		"sm_change_type_c");
    smio_map_key(SM_META('t'), 		"sm_transpose_words_c");
    smio_map_key(SM_META('W'), 		"sm_where_c");
    smio_map_key(SM_META('w'), 		"sm_copy_field_c");
    smio_map_key(SM_META('X'), 		"sm_sort_c");
    smio_map_key(SM_META('x'), 		"sm_sort_c");
    smio_map_key(SM_META('Y'), 		"sm_yank_data_c");
    smio_map_key(SM_META('y'), 		"sm_yank_data_c");
    smio_map_key(SM_META(SM_CTRL('Y')), "sm_yes_c");
    smio_map_key(SM_META('Z'), 		"sm_exit_c");
    smio_map_key(SM_META('z'), 		"sm_exit_c");
    smio_map_key(SM_META('!'), 		"sm_shell_out_c");
    smio_map_key(SM_META('\177'), 	"sm_del_word_back_c");
    
    smio_map_key(SM_LEFT_K, 		"sm_left_c");	
    smio_map_key(SM_RIGHT_K,		"sm_right_c");	
    smio_map_key(SM_LEFT_WORD_K, 	"sm_left_word_c");	
    smio_map_key(SM_RIGHT_WORD_K, 	"sm_right_word_c");	
    smio_map_key(SM_HOME_SCREEN_K,	"sm_home_screen_c");
    smio_map_key(SM_END_SCREEN_K, 	"sm_end_screen_c");
    smio_map_key(SM_FIRST_SCREEN_K,	"sm_first_screen_c");
    smio_map_key(SM_LAST_SCREEN_K, 	"sm_last_screen_c");
    smio_map_key(SM_UP_K,		"sm_up_c");	
    smio_map_key(SM_DOWN_K,		"sm_down_c");	
    smio_map_key(SM_PGUP_K,		"sm_prev_screen_c");
    smio_map_key(SM_PGDN_K,		"sm_next_screen_c");
    smio_map_key(SM_HOME_K,		"sm_home_field_c");	
    smio_map_key(SM_END_K,		"sm_end_field_c");	
    smio_map_key(SM_INS_K,		"sm_toggle_ins_c");
    smio_map_key(SM_DEL_K,		"sm_del_char_c");
    smio_map_key(SM_TAB_K,		"sm_next_field_c");	
    smio_map_key(SM_ENTER_K,		"sm_next_line_c");	
    smio_map_key(SM_RET_K,		"sm_next_line_c");	
    smio_map_key(SM_BS_K,		"sm_del_back_c");	
    smio_map_key(SM_ESC_K,		"sm_quit_c");	
    smio_map_key(SM_CLEAR_EOL_K,  	"sm_clear_field_c");
    smio_map_key(SM_CLEAR_EOS_K, 	"sm_clear_screen_c");
    smio_map_key(SM_MOUSE_EVENT_K, 	"sm_mouse_event_c");
    
    smio_init_callback();
}

#if SYSTEM_CANT_REDIRECT
/* This crock of shit is needed because MSDOG can't handle > (nor 2>, for
   that matter) in a system call. Jeez.
*/
int msdog_system ARG1(char *, command)
{
    FILE	*f;
    char	*c, *batch_file_name = "$$junk$$.bat";
	
    f = fopen(batch_file_name, "w");
    while ((c = strchr(command, '/')) != NULL)
    	*c = '\\';
    fputs("@echo off\n", f);
    fputs("cls\n", f);
    fputs(command, f);
    fclose(f);
    system(batch_file_name);
    unlink(batch_file_name);
    return(0);
}
#endif /* SYSTEM_CANT_REDIRECT */

/* Execute an external command - oddly, vi and less do weird things
   if stderr is redirected. Hence the need for redirect_stderr. */
int smio_system ARG2(CHAR *, buf, int, redirect_stderr)
{
    CHAR	error[80];
    struct stat	statbuf;
    int		retval;

    retval = 0;

    if (smio_using_curses) {
	smio_message(buf);

#if !SYSTEM_CANT_REDIRECT
	/* The alternative is that error messages just mess up the screen */
	if (redirect_stderr) {
	    strcat(buf, " 2> ");
	    sm_mktemp(error, "EXXXXXX"); /* the error file name */
	    strcat(buf, error);
	}
#endif

	smio_susp();

#if SYSTEM_CANT_REDIRECT
	retval = msdog_system(buf);
#else
	retval = system(buf);
#endif
	
	smio_res();
	smio_message("");
	
	/* check for errors */

#if !SYSTEM_CANT_REDIRECT
	if (redirect_stderr && stat(error, &statbuf) == 0) {
	    if (statbuf.st_size) {
		FILE	*f = fopen(error, "r");
		if (f) {
		    fgets(buf, 100, f);
		    fclose(f);
		    if (strchr(buf, '\n'))
			*strchr(buf, '\n') = 0;
		    sm_message(buf);
		}
		retval = 1;
	    }
	}
	if (redirect_stderr)
	    unlink(error);
#endif

    }
    return(retval);
}

void	smio_term()
{
    if (smio_using_curses) { /* embraces XVIEW too */
	touchwin(stdscr);
	move(0, 0);
	clrtobot();
	refresh(); /* to clear screen */
	endwin();
    }
#ifdef SM_XVIEW
    if (smio_using_xview)
	smxview_term();
    else
#endif
#ifdef SM_XAW
    if (smio_using_xaw)
	smxaw_term();
    else
#endif
    sm_destroy_all_keywords(main_keymap);
    freeArray(main_keymap);
    smio_using_curses = 0;
}

int	smio_popup ARG4(int, num_rows, int, num_cols, int, row, int, col)
{
    WINDOW	*new_window;

#ifdef SM_XVIEW
    if (smio_using_xview)
	return(smxview_popup(num_rows, num_cols, row, col));
#endif

#ifdef SM_XAW
    if (smio_using_xaw)
	return(smxaw_popup(num_rows, num_cols, row, col));
    /* smxaw_error("Called _popup!"); */
#endif

    if (!smio_using_curses)
	return(SM_ERROR);

    if (top_of_wstack < (SM_MAX_POPUP - 1))
	if ((new_window = newwin(num_rows, num_cols, row, col)) != NULL) {
	    wstack[++top_of_wstack].window = new_window;
	    keypad(wstack[top_of_wstack].window, TRUE);
	    meta(wstack[top_of_wstack].window, TRUE);
	    wstack[top_of_wstack].num_rows = num_rows;
	    wstack[top_of_wstack].num_cols = num_cols;
	    wstack[top_of_wstack].origin_row = row;
	    wstack[top_of_wstack].origin_col = col;

	    if (top_of_wstack) {
		wstack[top_of_wstack].num_rows -= 2;
		wstack[top_of_wstack].num_cols -= 2;
	    }
	    smio_goto(top_of_wstack, 0, 0);
	    smio_clear(top_of_wstack);
	    return(top_of_wstack);
	} else
	    return(SM_ERROR);
    else
	return(SM_ERROR);
}

/* Delete the popped window */
void	smio_delwin ARG1(int, window)
{
#ifdef SM_XVIEW
    if (smio_using_xview)
	smxview_delwin(window);
    else
#endif

#ifdef SM_XAW
    if (smio_using_xaw)
	smxaw_delwin(window);
    /* smxaw_error("Called _delwin!"); */
    else
#endif

    if (smio_using_curses) {
	delwin(wstack[top_of_wstack].window);
	wstack[top_of_wstack].window = NULL;
	top_of_wstack--;
	touchwin(wstack[top_of_wstack].window);
	wrefresh(wstack[top_of_wstack].window);
    }
}

void	smio_clear ARG1(int, window)
{
#ifdef SM_XVIEW
    if (smio_using_xview)
	smxview_clear(window);
    else
#endif

#ifdef SM_XAW
    if (smio_using_xaw)
	smxaw_clear(window);
    else
#endif

    if (smio_using_curses) {
	wmove(wstack[window].window, 0, 0);
	wclrtobot(wstack[window].window);
	if (window)
	    smio_box(window);
    }
}

void	smio_clrline ARG1(int, window)
{
    if (smio_using_curses) {
	int	row, col;

	smio_getyx(window, &row, &col);
	smio_goto(window, row, 0);
	wclrtoeol(wstack[window].window);
	if (window)
	    smio_box(window);
    }
}

void 	smio_getyx ARG3(int, window, int *, row, int *, col)
{
    
#ifdef SM_XVIEW
    if (smio_using_xview)
	smxview_getyx(window, row, col);
    else
#endif
	
#ifdef SM_XAW
    if (smio_using_xaw)
	smxaw_getyx(window, row, col);
    else
#endif
	
	if (smio_using_curses) {
	    getyx(wstack[window].window, *row, *col);
	    if (window) {
		(*row) -= 1;
		(*col) -= 1;
	    }
	}
}

void	smio_enq ARG3(int, window, int *, row, int * ,col)
{
#ifdef SM_XVIEW
    if (smio_using_xview)
	smxview_enq(window, row, col);
    else {
#endif
#ifdef SM_XAW
    if (smio_using_xaw)
	smxaw_enq(window, row, col);
    else {
#endif
    if (window < 0) /* Then use top window */
	window = top_of_wstack;
    *row = wstack[window].num_rows;
    *col = wstack[window].num_cols;
#if SM_XVIEW || SM_XAW
    }
#endif
}

void	smio_enq_screen ARG2(int *, row, int *, col)
{
#ifdef SM_XVIEW
    if (smio_using_xview)
	smxview_enq_screen(row, col);
    else
#endif
#ifdef SM_XAW
    if (smio_using_xaw)
	smxaw_enq_screen(row, col);
    else
#endif
	if (smio_using_curses) {
	    *row = LINES - 3;
	    *col = COLS;
	}
}

void	smio_goto ARG3(int, window, int, row, int, col)
{
#ifdef SM_XVIEW
    if (smio_using_xview)
	smxview_goto(window, row, col);
    else 
#endif
#ifdef SM_XAW
    if (smio_using_xaw)
	smxaw_goto(window, row, col);
    /* smxaw_error("Called _goto!"); */
    else 
#endif
	if (smio_using_curses) {
	    
	    if ((row < wstack[window].num_rows) &&
		(col < wstack[window].num_cols)) {
		
		/* Take care of box characters */
		if (window) {
		    row++;
		    col++;
		}
		
		wmove(wstack[window].window, row, col);
		cursor_valid = TRUE;
	    } else
		cursor_valid = FALSE;
	}
}

static int	enh_table[][2] = {
    {SM_NORMAL,		0},
    {SM_INVERSE,	A_REVERSE},
    {SM_STANDOUT,	A_STANDOUT},
    {SM_BLINK,		A_BLINK},
    {SM_UNDER,		A_UNDERLINE},
    {SM_BOLD,		A_BOLD},
    {-1,		-1}
};

void	smio_enh ARG2(int, window, ENH, enhancement)
{
    int	i, mapped_enh = 0;
    
#ifdef SM_XVIEW
    if (smio_using_xview) {
	smxview_enh(window, enhancement);
    } else
#endif
#ifdef SM_XAW
    if (smio_using_xaw) {
	smxaw_enh(window, enhancement);
    } else
#endif
	if (smio_using_curses) {
	    for (i=0; enh_table[i][0] != -1; i++)
		if (enh_table[i][0] == enhancement) {
		    mapped_enh = enh_table[i][1];
		    break;
		}
	    wattrset(wstack[window].window, mapped_enh);
	}
}

void	smio_puts ARG2(int, window, CHAR *, s)
{
    int		y, x, maxlen;
    CHAR	saved;
    
#ifdef SM_XVIEW
    if (smio_using_xview)
	smxview_puts(window, s);
    else
#endif
#ifdef SM_XAW
    if (smio_using_xaw)
	smxaw_puts(window, s);
    /* smxaw_error("Called _puts!"); */
    else
#endif
	if (!smio_using_curses)
	    puts(s);
	else {
	    
	    smio_getyx(window, &y, &x);
	    
	    maxlen = wstack[window].num_cols - x;
	    if (cursor_valid && (maxlen > 0)) {
		if (strlen(s) > maxlen) {
		    saved = s[maxlen];
		    s[maxlen] = 0;
		} else
		    saved = 0;
		
		waddstr(wstack[window].window, s);
		
		if (saved)
		    s[maxlen] = saved;
	    }
	}
}

static void meta_on()
{
    if (smio_using_curses) {
	wmove(fkey_window, 0, 40);
	waddstr(fkey_window, "M-");
    }
#if SM_XAW
    else
	smxaw_meta_on();
#endif
}

static void meta_off()
{
    if (smio_using_curses) {
	wmove(fkey_window, 0, 40);
	waddstr(fkey_window, "  ");
    }
#if SM_XAW
    else
	smxaw_meta_off();
#endif
}

static void update_insert()
{
    if (smio_using_curses) {
	wmove(fkey_window, 0, 36);
	if (smio_insert_state)
	    waddstr(fkey_window, "Ins");
	else
	    waddstr(fkey_window, "   ");
    } /* Non-curses version handled by 'got_a_command()' */
}

static int smio_key_to_command ARG1(int, key)
{
    CHAR	*command_name;

    if (sm_use_menus && (key & SM_META(0)) && smio_root_menu) {
	INDEX		item;
	SM_MENU		menu = smio_root_menu;
	SM_MENU_ITEM	menu_item;
	int		shift_key;
	    
	shift_key = key & ~SM_META(0);
	if (islower(shift_key))
	    shift_key = toupper(shift_key);
	else
	    shift_key = tolower(shift_key);
	shift_key = SM_META(shift_key);

	for (item = 0; item < arrayLength(menu->items); item++) {
	    menu_item = (SM_MENU_ITEM)arrayItem(menu->items, item);
	    if ((key == menu_item->key) || (shift_key == menu_item->key)) {
		if (*(menu_item->command) == '>') {
		    smio_current_argument = menu_item->command;
		    smio_current_start = menu_item->start;
		    return(SM_POPUP_MENU_C);
		} else
		    return(sm_lookup_keyword(sm_command_table,
					     menu_item->command));
	    }
	}
    }

    if ((command_name = sm_lookup_string(main_keymap, key)))
	return(sm_lookup_keyword(sm_command_table, command_name));
    else
	return(0);
}

static char *fmt_curses ARG2(CHAR *, buf, int, i)
{
    *buf = 0;

    /* Convert 'curses' input characters to sm conventions */
    if (i > 0xFF) {
	
	if (i == SM_MOUSE_EVENT_K)
	    strcpy(buf, "Mouse event");
	
	else if ((i >= KEY_F(1)) && (i <= KEY_F(NUM_FKEYS)))
	    sprintf(buf, "F%d", (i - KEY_F(1)));
	
	else switch (i) {
	case KEY_DOWN:		strcpy(buf, "KEY_DOWN");	break;	
	case KEY_UP:		strcpy(buf, "KEY_UP");		break;
	case KEY_LEFT:		strcpy(buf, "KEY_LEFT");	break;	
	case KEY_RIGHT:		strcpy(buf, "KEY_RIGHT");	break;	
	case KEY_HOME:		strcpy(buf, "KEY_HOME");	break;	
	case KEY_BACKSPACE:	strcpy(buf, "KEY_BACKSPACE");	break;	
	case KEY_DL:		strcpy(buf, "KEY_DL");		break;
	case KEY_IL:		strcpy(buf, "KEY_IL");		break;
	case KEY_DC:		strcpy(buf, "KEY_DC");		break;
	case KEY_IC:		strcpy(buf, "KEY_IC");		break;
	case KEY_EIC:		strcpy(buf, "KEY_EIC");		break;
	case KEY_CLEAR:		strcpy(buf, "KEY_CLEAR");	break;	
	case KEY_EOS:		strcpy(buf, "KEY_EOS");		break;
	case KEY_EOL:		strcpy(buf, "KEY_EOL");		break;
	case KEY_SF:		strcpy(buf, "KEY_SF");		break;
	case KEY_SR:		strcpy(buf, "KEY_SR");		break;
	case KEY_NPAGE:		strcpy(buf, "KEY_NPAGE");	break;	
	case KEY_PPAGE:		strcpy(buf, "KEY_PPAGE");	break;	
	case KEY_STAB:		strcpy(buf, "KEY_STAB");	break;	
	case KEY_CTAB:		strcpy(buf, "KEY_CTAB");	break;	
	case KEY_CATAB:		strcpy(buf, "KEY_CATAB");	break;	
	case KEY_ENTER:		strcpy(buf, "KEY_ENTER");	break;	
	case KEY_SRESET:	strcpy(buf, "KEY_SRESET");	break;	
	case KEY_RESET:		strcpy(buf, "KEY_RESET");	break;	
	case KEY_PRINT:		strcpy(buf, "KEY_PRINT");	break;	
	case KEY_LL:		strcpy(buf, "KEY_LL");		break;
	case KEY_END:		strcpy(buf, "KEY_END");		break;
	case KEY_F(NUM_FKEYS+1):	strcpy(buf, "META");		break;
#ifdef KEY_SLEFT
	case KEY_SLEFT:		strcpy(buf, "KEY_SLEFT");	break;
	case KEY_SRIGHT:	strcpy(buf, "KEY_SRIT");	break;
	case KEY_SPREVIOUS:	strcpy(buf, "KEY_SPREVIOUS");	break;
	case KEY_SNEXT:		strcpy(buf, "KEY_SNEXT");	break;
	case KEY_SHOME:		strcpy(buf, "KEY_SHOME");	break;
	case KEY_SBEG:		strcpy(buf, "KEY_SBEG");	break;
	case KEY_SEND:		strcpy(buf, "KEY_SEND");	break;
#endif
	default:		*buf = 0;			break;
	}
    }

    return(&buf[strlen(buf)]);
}

static int map_curses ARG1(int, i)
{
    /* Convert 'curses' input characters to sm conventions 
       Returns 0 in order to ignore a key.
       Returns -1 if curses does not map the key.
       Note that Sun /usr/5include/curses.h and ncurses.h have defines in the
       'META' range - thus we need to check if a returned value is
       mapped by curses _before_ checking if it's META- !!
    */

    if (i > 0xFF) {
	if (i == SM_MOUSE_EVENT_K)
	    i = SM_MOUSE_EVENT_K; /* !! */
	
	else if ((i >= KEY_F(1)) && (i <= KEY_F(NUM_FKEYS)))
	    i = SM_META((i - KEY_F(1) + '1')); /* = Meta-1 etc */
	
	else switch (i) {
	case KEY_DOWN:		i = SM_DOWN_K;			break;	
	case KEY_UP:		i = SM_UP_K;			break;
	case KEY_LEFT:		i = SM_LEFT_K;			break;	
	case KEY_RIGHT:		i = SM_RIGHT_K;			break;	
	case KEY_HOME:		i = SM_HOME_K;			break;	
	case KEY_BACKSPACE:	i = SM_BS_K;			break;	
	case KEY_DL:		i = 0;				break;
	case KEY_IL:		i = 0;				break;
	case KEY_DC:		i = SM_DEL_K;			break;
	case KEY_IC:		i = SM_INS_K;			break;
	case KEY_EIC:		i = SM_INS_K;			break;
	case KEY_CLEAR:		i = 0;				break;	
	case KEY_EOS:		i = SM_CLEAR_EOL_K;		break;
	case KEY_EOL:		i = SM_CLEAR_EOS_K;		break;
	case KEY_SF:		i = 0;				break;
	case KEY_SR:		i = 0;				break;
	case KEY_NPAGE:		i = SM_PGDN_K;			break;	
	case KEY_PPAGE:		i = SM_PGUP_K;			break;	
	case KEY_STAB:		i = 0;				break;	
	case KEY_CTAB:		i = 0;				break;	
	case KEY_CATAB:		i = 0;				break;	
	case KEY_ENTER:		i = SM_ENTER_K;			break;	
	case KEY_SRESET:	i = 0;				break;	
	case KEY_RESET:		i = 0;				break;	
	case KEY_PRINT:		i = 0;				break;	
	case KEY_LL:		i = SM_END_K;			break;
	case KEY_END:		i = SM_END_K;			break;
	case KEY_F(NUM_FKEYS+1):i = '\033'; /* Meta- */		break;
#ifdef KEY_SLEFT /* Shifted keypad keys .... */
	case KEY_SLEFT:		i = SM_LEFT_WORD_K;		break;
	case KEY_SRIGHT:	i = SM_RIGHT_WORD_K;		break;
	case KEY_SPREVIOUS:	i = SM_HOME_SCREEN_K;		break;
	case KEY_SNEXT:		i = SM_END_SCREEN_K;		break;
	case KEY_SHOME:		i = SM_FIRST_SCREEN_K;		break;
	case KEY_SBEG:		i = SM_FIRST_SCREEN_K;		break;
	case KEY_SEND:		i = SM_LAST_SCREEN_K;		break;
#endif
	default:		i = -1;				break;
	}
    } else
	i = -1;
    
    return(i);
}

static char *fmt_key ARG2(CHAR *, buf, int, c)
{
    CHAR *p;

    if (c & SM_META(0)) {
	strcpy(buf, "Meta-");
	c &= ~SM_META(0);
    } else
	buf[0] = 0;

    if (c > 0xFF)
	fmt_curses(buf, c);
    else {
	if (c < ' ') {
	    strcat(buf, "^");
	    c += '@';
	}
	if (c <= '~') {
	    p = &buf[strlen(buf)];
	    *p++ = c;
	    *p = 0;
	} else if (c == '\177')
	    strcat(buf, "del");
    }
    return(&buf[strlen(buf)]);
}

static int is_meta ARG1(int, i)
{
    if ((i == '\033' /* Escape */ ) || (i == '\037' /* ^_ */))
	return(TRUE);
    else
	return(FALSE);
}

/* display a softkey label or (if fkey_num < 0) turn them all off */
static void fkey_puts ARG3(int, fkey_num, CHAR *, upper, CHAR *, lower)
{
    int	col = fkey_num * 9 + (fkey_num > 3? 8: 0);

    if (fkey_num < 0) {
	wmove(fkey_window, 0, 0);
	wclrtobot(fkey_window);
    } else {
	wmove(fkey_window, 0, col);
	wattrset(fkey_window, A_REVERSE);
	waddstr(fkey_window, upper);
	if (fkey_num > 0) {
	    wattrset(fkey_window, 0);
	    wmove(fkey_window, 0, col - 1);
	    waddch(fkey_window, '1' + fkey_num);
	}
	wattrset(fkey_window, A_REVERSE);
	wmove(fkey_window, 1, col);
	waddstr(fkey_window, lower);
	wattrset(fkey_window, 0);
    }
    update_insert();
}

static void paint_skey ARG2(SM_SOFTKEY, softkey_set, int, i)
{
    if (softkey_set->toggle[i] != SM_NOT_TOGGLE) {
	if (softkey_set->toggle[i])
	    softkey_set->upper[i][7] = '*';
	else
	    softkey_set->upper[i][7] = ' ';
    }
    
#ifdef SM_XVIEW
    if (smio_using_xview)
        smxview_paint_skey(i, softkey_set->upper[i], softkey_set->lower[i]);
    else
#endif
#ifdef SM_XAW
	if (smio_using_xaw)
	    smxaw_paint_skey(i, softkey_set->upper[i], softkey_set->lower[i]);
	else
#endif
	    fkey_puts(i, softkey_set->upper[i], softkey_set->lower[i]);
}

/* Returns -1 for uninteresting keystrokes */
static int map_keystroke ARG2(int, command, int, do_it)
{
    int		i;

#if 0 /* Debugging */
    {
	CHAR 	buf[30];
	
	sprintf(fmt_key(buf, command), "0x%x", command);
	smio_message(buf);
    }
#endif /* 0 */

#ifdef SM_MOUSE
#if SM_XVIEW || SM_XAW
    if (smio_using_curses)
#endif
    {
	/* Catch the special sequences inserted into the input stream
	   by the ctool program (part of this distribution) or by the hacked
	   version of PC-Curses. The special sequence is 
	   'Ctrl-X NULL ( Button# Col# Row# TimeDelta )' 	 */

	if ((command == '\030') && do_it) { /* ^X */
	    command = wgetch(wstack[top_of_wstack].window);
	    if ((command != 0) && (command != 12)) {  /* ^@ or ^L */
		pushed_char = command;
		command = '\030';
	    } else if (command == 12) { /* Possibly an X repaint */
		if (smio_using_curses) {
		    endwin();
		    initscr();
		    touchwin(stdscr);
		    move(0, 0);
		    clrtobot();
		    refresh(); /* to clear screen */
		    if ((LINES < 24) || (COLS < 80))
			smio_abort("Need at least 24 rows and 80 columns.");
		    /* Use raw instead of cbreak() ... it allows ^C etc to pass
		       through instead of giving an interrupt */
		    raw();
		    nonl();
		    noecho();
		    for (i = 0; i <= top_of_wstack; i++) {
			wstack[i].window = newwin(wstack[i].num_rows + i?2:0, 
						  wstack[i].num_cols + i?2:0, 
						  wstack[i].origin_row, 
						  wstack[i].origin_col);
			if (wstack[i].window == NULL)
			    smio_abort("Can't recreate windows after REPAINT");
			keypad(wstack[i].window, TRUE);
			meta(wstack[i].window, TRUE);
		    }
		    if ((fkey_window = newwin(2, COLS, LINES-2, 0)) == NULL)
			smio_abort("Can't recreate softkey label window after REPAINT.");
		    
		    if ((msg_window = newwin(1, COLS, LINES-3, 0)) == NULL)
			smio_abort("Can't recreate message window after REPAINT.");
		}
		return(SM_REPAINT_C);
	    } else { /* A mouse hit */
		CHAR	buf[100], *pos;

		pos = buf;
		while ((*pos = wgetch(wstack[top_of_wstack].window)) != '\015')
		    pos++;
		*pos = 0;
		sscanf(buf, "(%d %d %d %d)",
		       &smio_button,
		       &smio_mouse_col,
		       &smio_mouse_row,
		       &smio_time_delta);
		if (smio_mouse_row < 0)
		    smio_mouse_row = 0;
		if (smio_mouse_col < 0)
		    smio_mouse_col = 0;
		command = SM_MOUSE_EVENT_K;
#ifdef SM_TURBOC
/* TURBOC version only returns UP buttons but they are coded as 1 */
		if (smio_button == 2)
		    smio_button = SM_MOUSE_UP | SM_MOUSE_RIGHT;
		else	
		    smio_button = SM_MOUSE_UP | SM_MOUSE_LEFT;
#endif		
		/*if ((smio_button & SM_MOUSE_MIDDLE) ||
		    (smio_button & SM_MOUSE_RIGHT))
			return(-1);*/

		/* Menu bar? */
		if (smio_mouse_row == 0 && sm_use_menus) {
		    INDEX		item;
		    SM_MENU		menu = smio_root_menu;
		    SM_MENU_ITEM	menu_item;

		    if (smio_mouse_col >= COLS - strlen(smio_menu_help_message))
			return(SM_HELP_C);

		    for (item = 0; item < arrayLength(menu->items); item++) {
			menu_item = (SM_MENU_ITEM)arrayItem(menu->items, item);
			if (smio_mouse_col < menu_item->start)
			    break;
		    }
		    item--;
		    
		    menu_item = (SM_MENU_ITEM)arrayItem(menu->items, item);
		    if (*(menu_item->command) == '>') {
			/* We have a menu popup to do */
			smio_current_argument = menu_item->command;
			command = SM_POPUP_MENU_C;
		    } else
			command = sm_lookup_command(menu_item->command);
		    return(command);
		}

		/* translate column to function key */
		if (smio_mouse_row > LINES - 3) {
		    if ((smio_mouse_col > 35) &&
			(smio_mouse_col < 44))
			return(-1); /* Ignore it */
		    else if (smio_button & SM_MOUSE_UP) {
			if (smio_mouse_col >= 44)
			    smio_mouse_col -= 8;
			command = KEY_F(1) + smio_mouse_col / 9;
		    } else
			/* Ignore DOWN fkeys - wait till released in case
			   he changes his mind */
			return(-1);
		} else {
		    if (top_of_wstack) {
			smio_mouse_col -= (wstack[top_of_wstack].origin_col+1);
			smio_mouse_row -= (wstack[top_of_wstack].origin_row+1)
			    + sm_use_menus;
		    }
		    if ((smio_button & SM_MOUSE_RIGHT)
#ifndef SM_TURBOC
			&& !(smio_button & SM_MOUSE_UP)
#endif
			)
			pushed_char = sm_lookup_keyword(main_keymap, 
							"sm_popup_c");

		}
	    }

        /* hard-coding for terminals that return ^[[Z. This is rather
         * bad - it should be done through correcting the termcap
         * entry and coding through curses. I'll leave it in for now.
         * Famous last words.
         */
        } else if (meta_flag && (command == '[')) { /* Shift+TAB */
	    command = wgetch(wstack[top_of_wstack].window);
            if (command != 'Z') {
		pushed_char = command;
		command = '[';
	    } else {
		command = SM_STAB_K;
		meta_flag = 0;
		meta_off();
	    }

        }
    }
#endif /* SM_MOUSE */

    if (smio_using_curses) {
	if ((i = map_curses(command)) >= 0)
	    command = i;
	else if (meta_flag)
	    command = SM_META(command);
    } else if (meta_flag)
	command = SM_META(command);
    
#if USE_NCURSES
    /* NCURSES sets all high bits to 1 on Linux! */
    if (command & 0x80)
	command &= 0xff;
#endif

    if (meta_flag) {
	meta_flag = 0;
	meta_off();
    }

    /* Plain characters, 8-bit characters */
    if (((command >= 0x20) && (command < 0x7f)) ||
        ((command >= 0xa0) && (command < 0xff)))
	return(command);
    
    if (command == SM_META('\033'))
	command = SM_ESC_K;
    
    /* Normalise meta characters */
    /* if (command & 0x80)
       command = 0x80 | (command & 0x7F); */
    
    /* Map SM_META(1) - SM_META(8) to f1-f8 */
    if ((command >= SM_META('1')) && (command <= SM_META('9')))
	command = command - SM_META('1') + SM_F1_K;
    
    /* Extract META- prefix from the stream */
    if (is_meta(command)) {
	meta_flag = 1;
	meta_on();
	command = 0;
	return(-1);
    }
    
    /* Handle softkey input - map to is and manage toggles */
    if (smio_current_skey && (command >= SM_F1_K) && (command <= SM_F8_K)) {
	int skeynum = command - SM_F1_K;

	smio_current_argument = smio_current_skey->key_argument[skeynum];

	if (do_it && (smio_current_skey->toggle[skeynum] != SM_NOT_TOGGLE)) {
	    smio_current_skey->toggle[skeynum] = 
		!(smio_current_skey->toggle[skeynum]);
	    paint_skey(smio_current_skey, skeynum);
	}
	command = sm_lookup_command(sm_skey_name(smio_current_skey, 
					   command - SM_F1_K));
	if (do_it) {
	    switch (command) {
	    case SM_NEXT_KEYS_C: 
		{
		    SM_SOFTKEY	skey;
		    
		    skey = 
			sm_skey_open(smio_current_skey->parent,
				     smio_current_skey->key_argument[skeynum]);
		    if (skey)
			sm_paint_skey(skey);
		}
		break;
	    case SM_EXEC_PROG_C:
	    case SM_EXEC_PROG_NO_ERR_C:
		{
		    CHAR 	*buf = alloca(SM_MAXREC);

		    smio_get_argument(buf);
		    if (*buf) {
			if (command == SM_EXEC_PROG_NO_ERR_C)
			    smio_system(buf, 0);
			else
			    smio_system(buf, 1);
                    }
		}
		command = -1;
		break;
	    default:
		break;
	    }
	}
	return(command);
    }
    
    /* Now convert characters to commands */
    command = smio_key_to_command(command);
    return(command);
}

static int print_key_help ARG4(int,		window,
			       int,	 	c,
			       int, 		row,
			       int,		col)
{
    CHAR      		buf[30], *help;
    int			d = c;
    int			i, command;
    
    if (smio_using_curses && (c > 0xFF)) {
	i = map_curses(c);
	if (i >= 0)
	    d = i;
    }
    if ((command = map_keystroke(d, 0)) && 
	 (help = sm_lookup_help(sm_command_table, command))) {
	smio_goto(window, row, col + 1);
	fmt_key(buf, c);
	smio_puts(window, buf);
	smio_puts(window, ": ");
	smio_puts(window, help);
	return(row + 1);
    }
    return(row);
}

#define START_ROW 2

static int ctrl_help ARG1(int, window)
{
    int		c, row = START_ROW, col = 0;
    
    row = print_key_help(window, '\177', row, col);
    for (c = SM_CTRL('A'); c <= SM_CTRL('Z'); c++) {
	if (row > wstack[top_of_wstack].num_rows - 2) {
	    if (col > 0)
		return(0); /* give up! */
	    row = START_ROW;
	    col = (wstack[top_of_wstack].num_cols - 2)/2 + 1;
	}
	row = print_key_help(window, c, row, col);
    }
    return(0);
}

static int alt_help ARG1(int, window)
{
    int	c, d, row = START_ROW, col = 0;
    
    row = print_key_help(window, SM_META('\177'), row, col);
    for (c = SM_META('a'), d = SM_META('A'); c <= SM_META('t'); c++, d++) {
	if (row > wstack[top_of_wstack].num_rows - 2) {
	    if (col > 0)
		return(0); /* give up! */
	    row = START_ROW;
	    col = (wstack[top_of_wstack].num_cols - 2)/2 + 1;
	}
	row = print_key_help(window, c, row, col);
	row = print_key_help(window, d, row, col);
    }
    return(0);
}

static int ctrl_alt_help ARG1(int, window)
{
    int	c, d, row = START_ROW, col = 0;
    
    for (c = SM_META('u'), d = SM_META('U'); c <= SM_META('z'); c++, d++) {
	if (row > wstack[top_of_wstack].num_rows - 2) {
	    if (col > 0)
		return(0); /* give up! */
	    row = START_ROW;
	    col = (wstack[top_of_wstack].num_cols - 2)/2 + 1;
	}
	row = print_key_help(window, c, row, col);
	row = print_key_help(window, d, row, col);
    }

    for (c = SM_META(SM_CTRL('A')); c <= SM_META(SM_CTRL('Z')); c++) {
	if (row > wstack[top_of_wstack].num_rows - 2) {
	    if (col > 0)
		return(0); /* give up! */
	    row = START_ROW;
	    col = (wstack[top_of_wstack].num_cols - 2)/2 + 1;
	}
	row = print_key_help(window, c, row, col);
    }
    return(0);
}

static int prevent_recursion = 0;
static int smio_key_lists()
{
#define NUM_SCREENS 3
    int			command, current_help = 0, keystroke;
    f_ptr 		f[NUM_SCREENS];
    int			window;
    SM_SOFTKEY		store_skey = smio_current_skey;
    
    f[0] = alt_help;
    f[1] = ctrl_alt_help;
    f[2] = ctrl_help;
    window = sm_popup_win(LINES - 3, COLS, 0, 0); /* Full screen window */
    
    while (1) {
	smio_clear(window);
	smio_goto(window, 0, 10);
	smio_puts(window, "Help - (N)ext, (P)revious, (K)ey help, Return");
	(*(f[current_help]))(window);
	smio_paint_skey(sm_key_help_skey);

	refresh_windows();
	keystroke = wgetch(wstack[top_of_wstack].window);
	command = map_keystroke(keystroke, 1);
	if (((command < 0200) || ((command >= 0xa0) && (command < 0xff))) &&
            islower(command))
	    command = toupper(command);

	switch (command) {
	case 'N':
	case SM_NEXT_FIELD_C:
	case SM_NEXT_SCREEN_C:
	    current_help++;
	    if (current_help > NUM_SCREENS - 1)
		current_help = 0;
	    break;

	case 'P':
	case SM_PREV_FIELD_C:
	case SM_PREV_SCREEN_C:
	    current_help--;
	    if (current_help < 0)
		current_help = NUM_SCREENS - 1;
	    break;

	default:
	    sm_del_win(window);
	    smio_paint_skey(store_skey);
	    return(0);
	}
    }
}

static int smio_help ARG1(int, i)
{
    SM_SOFTKEY	store_skey = smio_current_skey;
    int		window, kwindow, command, keystroke, c;

    if (prevent_recursion)
	return(0);
    prevent_recursion = 1;

    window = sm_popup_win(LINES - 3, COLS, 0, 0); /* Full screen window */

    while (1) {
	sm_paint_scr(window, sm_help_scr, 1);

	refresh_windows();
	keystroke = wgetch(wstack[top_of_wstack].window);
	command = map_keystroke(keystroke, 1);
	if (((command < 0200) || ((command >= 0xa0) && (command < 0xff))) &&
            islower(command))
	    command = toupper(command);

	switch (command) {
	case SM_KEY_LISTS_C:
	    smio_key_lists();
	    break;

	case SM_KEY_HELP_C:
	    kwindow = sm_popup_win(4, 40, 10, 20);
	    smio_goto(kwindow, 0, 0);
	    smio_puts(kwindow, "Press a key (or return)");
	    do {
		refresh_windows();
		c = wgetch(wstack[top_of_wstack].window);
		smio_goto(kwindow, 1, 0);
		smio_clrline(kwindow);
		if (is_meta(c)) {
		    meta_flag = 1;
		    smio_puts(kwindow, "Meta-");
		} else {
		    if (meta_flag) {
			meta_flag = 0;
			c = SM_META(c);
		    }
		    if (print_key_help(kwindow, c, 1, 0) == 1) {
			CHAR	buf[20];

			smio_goto(kwindow, 1, 0);
			fmt_key(buf, c);
			smio_puts(kwindow, buf);
		    }
		}
	    } while ((c != '\n') &&
		     (c != '\r') &&
		     (c != 3));
	    sm_del_win(kwindow);
	    break;

	case SM_HELP_MANUAL_C:
	    {
		CHAR	*buf = alloca(100);
		int	flag = 0;
#if HAS_NO_MAN
		CHAR	*p;

		if ((p = getenv("PAGER")) == NULL)
		    p = "smmore";
		sprintf(buf, "%s %s%s.man", p, sm_program_directory, sm_program_name);
		flag = 1;
#else
		sprintf(buf, "man %s", sm_program_name);
#endif
		smio_system(buf, flag);
	    }
	    break;

	case SM_SCRMGR_MANUAL_C:
	    {
		CHAR	*buf = alloca(100);
		int	flag = 0;
#if HAS_NO_MAN
		CHAR	*p;

		if ((p = getenv("PAGER")) == NULL)
		    p = "smmore";
		sprintf(buf, "%s %sscrmgr.man", p, sm_program_directory);
		flag = 1;
#else
		sprintf(buf, "man scrmgr");
#endif
		smio_system(buf, flag);
	    }
	    break;

	case -1: /* Not an interesting key */
	    break;

	default:
	    smio_delwin(window);
	    smio_paint_skey(store_skey);
	    prevent_recursion = 0;
	    return(0);
	    break;
	}
    }
}

void smio_null_skey()
{
    fkey_puts(-1, NULL, NULL);
}

void smio_paint_skey ARG1(SM_SOFTKEY, softkey_set)
{
    int	i;

    for (i = 0; i < 8; i++)
	paint_skey(softkey_set, i);

    smio_current_skey = softkey_set;
}

static int paint_menu_item ARG1(CHAR *, text)
{
    int		retval = 0;

    waddch(menu_window, ' ');
    retval++;
    while (*text) {
	if (*text == '\\')
	    wattrset(menu_window, A_NORMAL | A_UNDERLINE);
	else {
	    waddch(menu_window, *text);
	    wattrset(menu_window, A_REVERSE);
	    retval++;
	}
	text++;
    }
    return(retval);
}

void smio_paint_menu ARG1(SM_MENU, menu)
{
    INDEX	item;
    int		len = 0;

    wmove(menu_window, 0, 0);
    wclrtoeol(menu_window);
    wattrset(menu_window, A_REVERSE);
    
    for (item = 0; item < arrayLength(menu->items); item++) {
	SM_MENU_ITEM	menu_item;

	menu_item = (SM_MENU_ITEM) arrayItem(menu->items, item);
	menu_item->start = len - 1;
	len += paint_menu_item(menu_item->text);
    }

    while (len++ < COLS - strlen(smio_menu_help_message) - 1)
	waddch(menu_window, ' ');
    paint_menu_item(smio_menu_help_message);

    smio_root_menu = menu;
}

void smio_get_argument ARG1(CHAR *, buf)
{
    *buf = 0;
    if (smio_current_argument && *smio_current_argument)
	sm_expand_fields(sm_current_scr(), buf, smio_current_argument);
}

/* This should only be called by the xv_main_loop equivalent command */
int smio_getch()
{
    int 	command = 0, keystroke = 0;
    int		row, col;

    if (!smio_using_curses)
	return(0);

    while (TRUE) {
	smio_getyx(0, &row, &col);
	wmove(fkey_window, 1, 36);
	wprintw(fkey_window, "%2.2d   %2.2d", row+1, col+1);
	refresh_windows();

	smio_button = 0;
	if (pushed_char) {
	    keystroke = pushed_char;
	    pushed_char = 0;
	} else
	    keystroke = wgetch(wstack[top_of_wstack].window);

	command = map_keystroke(keystroke, 1);

	/* Anything to execute at this level? */
	switch (command) {
	case SM_TOGGLE_INS_C:
	    smio_insert_state = !smio_insert_state;
	    update_insert();
	    break;
	case SM_REFRESH_C:
	    smio_refresh();
	    break;
	case SM_HELP_C:
	    command = smio_help(command);
	    break;
	case SM_TELL_VERSION_C:
	    smio_message(smio_rcsid());
	    break;
	case SM_SHELL_OUT_C: 
	    {
		CHAR	*p, buf[100];
		
		if (((p = getenv("SHELL")) != NULL) && *p)
		    strcpy(buf, p);
		else
#ifdef USE_COMMAND
		    strcpy(buf, "command");
#else
		strcpy(buf, "sh");
#endif
		smio_system(buf, 0);
	    }
	    break;
	case 0:
	    smio_bell();
	    break;
	default:
	    if (command > 0)
		return(command);
	    break;
	}
    }
}

int smio_enq_mouse ARG3(int *, row, int *, col, int *, time_delta)
{
#ifdef SM_XVIEW
    if (smio_using_xview)
	return(smxview_mouse(row, col, time_delta));
#endif
#ifdef SM_XAW
    if (smio_using_xaw)
	return(smxaw_enq_mouse(row, col, time_delta));
#endif
    if (smio_button) {
/* This is now done in smio_getch ...
	*row = smio_mouse_row -
	    wstack[top_of_wstack].origin_row -
		(top_of_wstack? 1: 0);
	if (*row < 0)
	    *row = 0;
	*col = smio_mouse_col -
	    wstack[top_of_wstack].origin_col -
		(top_of_wstack? 1: 0);
	if (*col < 0)
	    *col = 0;
*/
	*row = smio_mouse_row;
	*col = smio_mouse_col;
	*time_delta = smio_time_delta;
    }
    return(smio_button);
}

void smio_push_command ARG1(int, command)
{
    if (pushed_char || pushed_command) /* Only one can be pushed */
	return;
    if (((command >= ' ') && (command < 0200)) ||
        ((command >= 0xa0) && (command < 0xff)))
	pushed_char = command;
    else
	pushed_command = command;
}

void smio_add_callback ARG2(f_ptr, f, void *, object)
{
    SMIO_CALLBACK		temp;

    if (smio_callback && f) {
	temp = (SMIO_CALLBACK) xmalloc((INDEX) sizeof(struct smio_callback_s));
	temp->lambda = f;
	temp->next = smio_callback;
	temp->object = object;
	smio_callback = temp;
    }
}

struct object_s {
    f_ptr	destructor;
    int		other_data;
};
typedef struct object_s *objectp;

int smio_destroy_callback()
{
    SMIO_CALLBACK		temp;
    
    if (smio_callback) {
	temp = smio_callback->next;
	if (smio_callback->object)
	    (* ((objectp)(smio_callback->object))->destructor) ((objectp)(smio_callback->object));
	free(smio_callback);
 	smio_callback = temp;
	return(1);
    } else
	return(0);
}

void smio_init_callback()
{
    if (smio_callback == NULL) {
	smio_callback = (SMIO_CALLBACK) xmalloc((INDEX) sizeof(struct smio_callback_s));
	smio_callback->lambda = NULL;
	smio_callback->next = NULL;
	smio_callback->object = NULL;
    }
}

struct popup_s {
    f_ptr	destructor;
    INDEX	index;
    SM_MENU	menu;
};

int popup_destructor ARG1(struct popup_s *, object)
{
    free(object);
    return(0);
}

CALLBACK(get_command_from_menu)
{
    struct popup_s *popup = (struct popup_s *)callback_data;

    /* Always called after sm_selection */
    if (command == SM_YES_C) {
	SM_MENU_ITEM	menu_item;

	menu_item = (SM_MENU_ITEM)arrayItem(popup->menu->items, popup->index);
	if (*(menu_item->command) == '>') {
	    smio_current_argument = menu_item->command;
	    pushed_command = SM_POPUP_MENU_C;
	} else
	    pushed_command = sm_lookup_command(menu_item->command);
	command = SM_POPCALLBACK;
    } else if (command == SM_RIGHT_C) {
    } else if (command == SM_LEFT_C) {
    } else if (command == SM_QUIT_C)
	command = SM_POPCALLBACK;
    else
	command = 0;

    if (command)
	smdb_delete_data(sm_prompt_scr);
    return(command);
}

static int smio_popup_menu()
{
    SM_MENU		menu;
    SM_MENU_ITEM	*menu_itemp;
    struct popup_s	*popup;
    
    menu = sm_menu_open(smio_root_menu->parent, smio_current_argument + 1);
    if (menu) {
	smdb_delete_data(sm_prompt_scr);
	/* Build list for this menu */
	
	for (menu_itemp = (SM_MENU_ITEM *) firstArrayItemp(menu->items); 
	     menu_itemp && *menu_itemp; 
	     menu_itemp = (SM_MENU_ITEM*) nextArrayItemp(menu->items, (void **) menu_itemp))
	    smdb_write(sm_prompt_scr, (*menu_itemp)->text);
	
	smdb_seek(sm_prompt_scr, (INDEX) 0);
	
	popup = (struct popup_s *)xmalloc((INDEX) sizeof(struct popup_s));
	popup->destructor = popup_destructor;
	popup->index = 0;
	popup->menu = menu;
	sm_add_callback(get_command_from_menu, popup);
	sm_setup_selection(sm_prompt_scr,
			   &popup->index,
			   1,
			   smio_current_start,
			   NULL,
			   NULL,
			   NULL);
	return(0);
    } else
	return(1);
}

int got_command_do_the_callback ARG1(int, command)
{
    while (1) {
	if (smio_callback && smio_callback->lambda) {
	    void		*object = NULL;
	    SMIO_CALLBACK	callback;
	    
	    /* Find most recent data frame ...*/
	    for (callback = smio_callback;
		 callback && !(callback->object);
		 callback = callback->next) ;
	    if (callback)
		object = callback->object;
	    
	    command = (*(smio_callback->lambda)) (command, object);
	} else /* no callback stack!! */
	    return(-1);
	
	if (command) {
	    smio_destroy_callback();
	    if (command == SM_POPCALLBACK)
		command = 0;
	    /* else, pass command to next callback */
	} else /* Read another character for the current callback */
	    return(0);
    }
}

#ifdef SM_XVIEW
int got_a_command ARG1(int, command)
{
    command = map_keystroke(command, 1);
    if (command >= 0)
	if (got_command_do_the_callback(command) < 0)
	    smxview_term();
    while (pushed_command) {
	command = pushed_command;
	pushed_command = 0;
	if (got_command_do_the_callback(command) < 0)
	    smxview_term();
    }
    return(0);
}
#endif

#ifdef SM_XAW
int got_a_command ARG1(int, command)
{
    smxaw_down_tools();
    command = map_keystroke(command, 1);
    /* Anything to execute at this level? */
    switch (command) {
    case SM_TOGGLE_INS_C:
	smio_insert_state = !smio_insert_state;
	smxaw_update_insert();
	break;
    case SM_REFRESH_C:
	smio_refresh();
	break;
    case SM_HELP_C:
	/*command = smio_help(command);*/
	break;
    case SM_TELL_VERSION_C:
        smio_message(smio_rcsid());
	break;
    case SM_SHELL_OUT_C: 
	{
	    CHAR	*p, buf[100];
	    
	    if (((p = getenv("SHELL")) != NULL) && *p)
		strcpy(buf, p);
	    else
		strcpy(buf, "sh");
	    smio_system(buf, 0);
	}
	break;
    case 0:
	smio_bell();
	break;
    default:
	if (command >= 0)
	    if (got_command_do_the_callback(command) < 0)
		smxaw_term();
	break;
    }
    while (pushed_command) {
	command = pushed_command;
	pushed_command = 0;
	if (got_command_do_the_callback(command) < 0)
	    smxaw_term();
    }
    smxaw_up_tools();
    return(0);
}
#endif

/* Rules for a callback routine ....

   1. It is called with 6 args:
   2. If it returns non-zero, it gets popped from stack.
      SM_POPCALLBACK does just this - no other action is taken.
      Another use for SM_POPCALLBACK is in lambda function processing in 
      sm_edit() 
   3. If it returns 0 it is left on the stack and another command
      is obtained for it.
   4. A (single) command can be pushed into the input stream by 
      sm_push_command()
   5. When a routine is popped from the stack, if it had data then the
      data's destructor is called.
   6. If a routine did not have data assigned when it was added, then
      the stack is searched for the most recent routine that did have
      data, and that data is passed. The data's destructor is only called
      when the original owner is popped.
   7. Ain't this a beaut?
*/
void smio_main_loop()
{
    int 		command;

    /* Flush any initially pushed commands */
    while (pushed_command) {
	command = pushed_command;
	pushed_command = 0;
	if (got_command_do_the_callback(command) < 0)
	    return;
    }

#ifdef SM_XVIEW
    if (smio_using_xview)
	smxview_main_loop(got_a_command);
    else 
#endif
#ifdef SM_XAW
    if (smio_using_xaw)
	smxaw_main_loop(got_a_command);
    else
#endif
    while (1) {
	alloca(0);
	if (pushed_command) {
	    command = pushed_command;
	    pushed_command = 0;
	} else {
	    command = smio_getch();
	    if (command == SM_POPUP_MENU_C) {
		smio_popup_menu();
		continue;
	    }
	    smio_message("");
	}

	if (got_command_do_the_callback(command) < 0)
	    return;
    }
}
	
void	smio_bell()
{
#ifdef SM_XVIEW
    if (smio_using_xview)
	smxview_beep();
    else
#endif
#ifdef SM_XAW
    if (smio_using_xaw)
	smxaw_beep();
    else
#endif
	beep();
}

void	smio_refresh()
{
#ifdef SM_XVIEW
    if (smio_using_xview)
	smxview_refresh();
    else
#endif
#ifdef SM_XAW
    if (smio_using_xaw)
	smxaw_refresh();
    else
#endif
	clearok(curscr, TRUE);
}

void smio_message ARG1(CHAR *, s)
{
#ifdef SM_XVIEW
    if (smio_using_xview)
	smxview_msg(s);
    else 
#endif
#ifdef SM_XAW
    if (smio_using_xaw)
	smxaw_msg(s);
    else 
#endif
    if (smio_using_curses) {
	wmove(msg_window, 0, 0);
	waddstr(msg_window, s);
	wclrtoeol(msg_window);
	wrefresh(msg_window);
    } else
	puts(s);
}

void smio_flushinp()
{
    if (smio_using_curses)
	flushinp();
}

/*
 * Right trim a column
 */
int smio_maxcol ARG2(int, window, int, col_desired)
{
    int	cols;
    
#ifdef SM_XVIEW
    if (smio_using_xview)
	return(smxview_maxcol(window, col_desired));
    else 
#endif
#ifdef SM_XAW
    if (smio_using_xaw)
	return(smxaw_maxcol(window, col_desired));
    else 
#endif
	if (smio_using_curses) {
	    cols = wstack[window].num_cols;
	    if (col_desired > cols)
		return(cols);
	    else
		return(col_desired);
	}
    return(0);
}

int smio_insertp()
{
    return(smio_insert_state); /* XView always in insert-mode */
}


/* Equivalent to a notice in xview - smio_getch is justified */
int smio_line_error ARG3(CHAR *, msg, CHAR *, offense, int, line_num)
{
    CHAR	s[200];
    int		i, window;
    
#ifdef SM_XVIEW
    if (smio_using_xview) {
	sprintf(s, "%s %d \"%s\"", "In line #", line_num, offense);
	return(smxview_line_error_notice(s, msg));
    } else
#endif
#ifdef SM_XAW
    if (smio_using_xaw) {
	sprintf(s, "%s\n%s %d \"%s\"", msg, "In line #", line_num, offense);
	/* return(smxaw_line_error_notice(s, msg)); */
	smxaw_error(s);
    } else
#endif
	{
	    window = sm_popup_win(3 + 2, 68 + 2, 5, 5);
	    smio_goto(window, 0, 0);
	    smio_puts(window, msg);
	    smio_goto(window, 1, 0);
	    smio_puts(window, "In line #");
	    sprintf(s, "%d \"", line_num);
	    smio_puts(window, s);
	    smio_puts(window, offense);
	    smio_puts(window, " \"");
	    smio_goto(window, 2, 0);
	    smio_puts(window, "Press any key to continue.");
	    smio_flushinp();
	    i = smio_getch();
	    sm_del_win(window);
	    return(i);
	}
}

/* Equivalent to a notice in xview - smio_getch is justified */
int smio_error ARG1(CHAR *, msg)
{
    int i, window;
    
#ifdef SM_XVIEW
    if (smio_using_xview)
	return(smxview_error_notice(msg));
    else
#endif
#ifdef SM_XAW
    if (smio_using_xaw)
      /* return(smxaw_error_notice(msg)); */
      smxaw_error(msg);		
    else
#endif
	{
	    window = sm_popup_win(3 + 2, 68 + 2, 5, 5);
	    smio_goto(window, 0, 0);
	    smio_puts(window, msg);
	    smio_goto(window, 2, 0);
	    smio_puts(window, "Press any key to continue.");
	    smio_flushinp();
	    i = smio_getch();
	    sm_del_win(window);
	    return(i);
	}
}

/* Equivalent to a notice in xview - smio_getch is justified.
   Can only return SM_YES_C, SM_NO_C or SM_CANCEL_C */
int smio_confirm ARG1(CHAR *, msg)
{
    int		answer, window;
    
#ifdef SM_XVIEW
    if (smio_using_xview)
	smxview_confirm_notice(msg);
    else {
#endif
#ifdef SM_XAW
    if (smio_using_xaw)
	return(smxaw_confirm(msg));
    else {
#endif
    
    window = smio_popup(3 + 2, 68 + 2, 5, 5);
    smio_goto(window, 0, 0);
    smio_puts(window, msg);
    
    smio_goto(window, 2, 0);
    smio_puts(window, "[Y]es, [N]o or [^C] to cancel.");

    smio_flushinp();
    while (TRUE) {
	answer = smio_getch();
	/* 'Invisible' codes from xctool ... */
	if (answer == SM_YES_C)
	    answer = 'Y';
	if (answer == SM_NO_C)
	    answer = 'N';
	if (isascii(answer))
	    answer = toupper(answer);
	if ((answer == 'Y') || (answer == 'N') || (answer == SM_QUIT_C))
	    break;
    }
    smio_delwin(window);
    return(answer == 'Y'? SM_YES_C: answer == 'N'? SM_NO_C: SM_CANCEL_C);
#if SM_XVIEW || SM_XAW
    }
#endif
}

/* For emacs:
 * Local Variables:
 * tab-width: 8
 * End:
 */
